/*
 * FreeModbus Libary: Linux Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id: portserial.c,v 1.3 2006/10/12 08:35:34 wolti Exp $
 */

/* ----------------------- Standard includes --------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>

#include "port.h"

/*
 * Tcgetattr 取属性(termios结构)
 Tcsetattr 设置属性(termios结构)
 cfgetispeed 得到输入速度
 Cfgetospeed 得到输出速度
 Cfsetispeed 设置输入速度
 Cfsetospeed 设置输出速度
 Tcdrain  等待所有输出都被传输
 tcflow  挂起传输或接收
 tcflush  刷清未决输入和/或输出
 Tcsendbreak 送BREAK字符
 tcgetpgrp 得到前台进程组ID
 tcsetpgrp 设置前台进程组ID

.串口配置流程
 1>保存原先串口配置，用tcgetattr(fd,&oldtio)函数
  struct termios newtio,oldtio;
    tcgetattr(fd,&oldtio);
 2>激活选项有CLOCAL和CREAD，用于本地连接和接收使用
 　 newtio.c_cflag | = CLOCAL | CREAD;
 3>设置波特率，使用函数cfsetispeed、cfsetospeed
         cfsetispeed(&newtio,B115200);
    cfsetospeed(&newtio,B115200);
 4>设置数据位，需使用掩码设置
    newtio.c_cflag &= ~CSIZE;
   newtio.c_cflag |= CS8;
 5>设置奇偶校验位，使用c_cflag和c_iflag.
   设置奇校验:
  newtio.c_cflag |= PARENB;
  newtio.c_cflag |= PARODD;
  newtio.c_iflag |= (INPCK | ISTRIP);
   设置偶校验:
  newtio.c_iflag |= (INPCK｜ISTRIP);
  newtio.c_cflag |= PARENB;
  newtio.c_cflag |= ~PARODD;
 6>设置停止位，通过激活c_cflag中的CSTOPB实现。若停止位为1，则清除CSTOPB，若停止位为2，则激活CSTOPB。
  newtio.c_cflag &= ~CSTOPB;
 7>设置最少字符和等待时间，对于接收字符和等待时间没有特别的要求时，可设为0：
    newtio.c_cc[VTIME] = 0;
  newtio.c_cc[VMIN]  = 0;
 8>处理要写入的引用对象
   tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到，但用户程序尚未读)或输出缓存(用户程序已经写，但尚未发送).
  int tcflush(int filedes,int quene)
  quene数应当是下列三个常数之一:
    *TCIFLUSH  刷清输入队列
    *TCOFLUSH  刷清输出队列
    *TCIOFLUSH 刷清输入、输出队列
  例如：tcflush(fd,TCIFLUSH);
 9>激活配置。在完成配置后，需要激活配置使其生效。使用tcsetattr()函数：
  int tcsetattr(int filedes,int opt,const struct termios *termptr);
   opt使我们可以指定在什么时候新的终端属性才起作用，
  *TCSANOW:更改立即发生
  *TCSADRAIN:发送了所有输出后更改才发生。若更改输出参数则应使用此选项
  *TCSAFLUSH:发送了所有输出后更改才发生。更进一步，在更改发生时未读的
             所有输入数据都被删除(刷清).
   例如: tcsetattr(fd,TCSANOW,&newtio);
.串口使用详解
.打开串口
 fd = open("/dev/ttyS0",O_RDWR | O_NOCTTY | O_NDELAY);
 参数--O_NOCTTY:通知linux系统，这个程序不会成为这个端口的控制终端.
              O_NDELAY:通知linux系统不关心DCD信号线所处的状态(端口的另一端是否激活或者停止).
 然后恢复串口的状态为阻塞状态，用于等待串口数据的读入，用fcntl函数:
  fcntl(fd,F_SETFL,0);  //F_SETFL：设置文件flag为0，即默认，即阻塞状态
 接着测试打开的文件描述符是否应用一个终端设备，以进一步确认串口是否正确打开.
  isatty(STDIN_FILENO);
.读写串口
 串口的读写与普通文件一样，使用read，write函数
  read(fd,buff,8);
  write(fd,buff,8);

 */

/* ----------------------- Modbus includes ----------------------------------*/
//#include "mb.h"
#include "mbport.h"
#include "mbconfig.h"

/* ----------------------- Defines  -----------------------------------------*/
#if MB_ASCII_ENABLED == 1
#define BUF_SIZE    513         /* must hold a complete ASCII frame. */
#else
#define BUF_SIZE    256         /* must hold a complete RTU frame. */
#endif

/* ----------------------- Static variables ---------------------------------*/
static int      iSerialFd = -1;
static BOOL     bRxEnabled;
static BOOL     bTxEnabled;

static ULONG    ulTimeoutMs;
static UCHAR    ucBuffer[BUF_SIZE];
static int      uiRxBufferPos;
static int      uiTxBufferPos;

static struct termios xOldTIO;

/* ----------------------- Function prototypes ------------------------------*/
static BOOL     prvbMBPortSerialRead( UCHAR * pucBuffer, USHORT usNBytes, USHORT * usNBytesRead );
static BOOL     prvbMBPortSerialWrite( UCHAR * pucBuffer, USHORT usNBytes );

/* ----------------------- Begin implementation -----------------------------*/

void setiSerialFd(int fd)
{
    iSerialFd = fd;
}


void
vMBPortSerialEnable( BOOL bEnableRx, BOOL bEnableTx )
{
    /* it is not allowed that both receiver and transmitter are enabled. */
    assert( !bEnableRx || !bEnableTx );

    if( bEnableRx )
    {
        ( void )tcflush( iSerialFd, TCIFLUSH );  ///清空输入缓存
        uiRxBufferPos = 0;
        bRxEnabled = TRUE;
    }
    else
    {
        bRxEnabled = FALSE;
    }
    if( bEnableTx )
    {
        bTxEnabled = TRUE;
        uiTxBufferPos = 0;
    }
    else
    {
        bTxEnabled = FALSE;
    }
}

static void (*enable_send)(bool);
static void (*enable_read)(bool);





BOOL
xMBPortSerialInit( UCHAR *ucPort, ULONG ulBaudRate, UCHAR ucDataBits,
                   eMBParity eParity , void (*en_send)(bool), void (*en_read)(bool))
{

    CHAR            szDevice[16];
    BOOL            bStatus = TRUE;

    struct termios  xNewTIO;
    speed_t         xNewSpeed;

    snprintf( szDevice, 16,ucPort );  // P2"/dev/ttyS%d"

//    printf("iSerialFd is: %s\n",szDevice);

    if( ( iSerialFd = open( szDevice, O_RDWR /*| O_NOCTTY*/ ) ) < 0 )  ///获取串口设备
    {
        vMBPortLog( MB_LOG_ERROR, "SER-INIT", "Can't open serial port %s: %s\n", szDevice,
                    strerror( errno ) );
    }
    else if( tcgetattr( iSerialFd, &xOldTIO ) != 0 )   ///保存原先串口配置，用tcgetattr
    {
        vMBPortLog( MB_LOG_ERROR, "SER-INIT", "Can't get settings from port %s: %s\n", szDevice,
                    strerror( errno ) );
    }
    else
    {
        enable_send = en_send;
        enable_read = en_read;
//        printf("iSerialFd is: %d\n",iSerialFd);
        bzero( &xNewTIO, sizeof( struct termios ) );

        xNewTIO.c_iflag |= IGNBRK | INPCK;
        xNewTIO.c_cflag |= CREAD | CLOCAL;
        switch ( eParity )
        {
        case MB_PAR_NONE:
            break;
        case MB_PAR_EVEN:
            xNewTIO.c_cflag |= PARENB;
            break;
        case MB_PAR_ODD:
            xNewTIO.c_cflag |= PARENB | PARODD;
            break;
        default:
            bStatus = FALSE;
        }
        switch ( ucDataBits )
        {
        case 8:
            xNewTIO.c_cflag |= CS8;
            break;
        case 7:
            xNewTIO.c_cflag |= CS7;
            break;
        default:
            bStatus = FALSE;
        }
        switch ( ulBaudRate )
        {
        case 9600:
            xNewSpeed = B9600;
            break;
        case 19200:
            xNewSpeed = B19200;
            break;
        case 38400:
            xNewSpeed = B38400;
            break;
        case 57600:
            xNewSpeed = B57600;
            break;
        case 115200:
            xNewSpeed = B115200;
            break;
        default:
            bStatus = FALSE;
        }
        if( bStatus )
        {
            if( cfsetispeed( &xNewTIO, xNewSpeed ) != 0 )
            {
                vMBPortLog( MB_LOG_ERROR, "SER-INIT", "Can't set baud rate %ld for port %s: %s\n",
                            ulBaudRate, strerror( errno ) );
            }
            else if( cfsetospeed( &xNewTIO, xNewSpeed ) != 0 )
            {
                vMBPortLog( MB_LOG_ERROR, "SER-INIT", "Can't set baud rate %ld for port %s: %s\n",
                            ulBaudRate, szDevice, strerror( errno ) );
            }
            else if( tcsetattr( iSerialFd, TCSANOW, &xNewTIO ) != 0 )
            {
                vMBPortLog( MB_LOG_ERROR, "SER-INIT", "Can't set settings for port %s: %s\n",
                            szDevice, strerror( errno ) );
            }
            else
            {
                vMBPortSerialEnable( FALSE, FALSE );
                bStatus = TRUE;
            }
        }
    }
    return bStatus;

}

BOOL
xMBPortSerialSetTimeout( ULONG ulNewTimeoutMs )
{
    if( ulNewTimeoutMs > 0 )
    {
        ulTimeoutMs = ulNewTimeoutMs;
    }
    else
    {
        ulTimeoutMs = 1;
    }
    return TRUE;
}

void
vMBPortClose( void )
{
    if( iSerialFd != -1 )
    {
        ( void )tcsetattr( iSerialFd, TCSANOW, &xOldTIO );   ///恢复原来串口配置
        ( void )close( iSerialFd );
        iSerialFd = -1;
    }
}

BOOL
prvbMBPortSerialRead( UCHAR * pucBuffer, USHORT usNBytes, USHORT * usNBytesRead )
{
    BOOL            bResult = TRUE;
    ssize_t         res;
    fd_set          rfds;
    struct timeval  tv;

    tv.tv_sec = 0;
    tv.tv_usec = 50000;
    FD_ZERO( &rfds );
    FD_SET( iSerialFd, &rfds );

    /* Wait until character received or timeout. Recover in case of an
     * interrupted read system call. */
    do
    {

       /* 判断描述符iSerialFd是否在给定的描述符集rfds中，通常配合select函数使用，由于select函数成功返回时会将未
        准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后，某个描述符是否准备好，
        以便进行接下来的处理操作。当描述符fd在描述符集fdset中返回非零值，否则，返回零。*/
        if( select( iSerialFd + 1, &rfds, NULL, NULL, &tv ) == -1 )///检查 iSerialFd 对应 的串口是否可读
        {
            if( errno != EINTR )
            {
                bResult = FALSE;
            }
        }
        else if( FD_ISSET( iSerialFd, &rfds ) )
        {
          /*  read()会把参数fd所指的文件传送nbyte个字节到buf指针所指的内存中。
            若参数nbyte为0，则read()不会有作用并返回0。返回值为实际读取到的字节数，如果返回0，
            表示已到达文件尾或无可读取的数据。错误返回0,并将根据不同的错误原因适当的设置错误码。*/

            if( ( res = read( iSerialFd, pucBuffer, usNBytes ) ) == -1 )
            {
                bResult = FALSE;
            }
            else
            {
//                printf("read is: error!!!!");
                *usNBytesRead = ( USHORT ) res;  ///读取到的字节数
                break;
            }
        }
        else
        {
            *usNBytesRead = 0;
            break;
        }
    } while( bResult == TRUE );
    return bResult;
}
#define START_SEND()     if(enable_read)     \
                            enable_read(FALSE);\
                           if(enable_send)     \
                            enable_send(TRUE); \
                           usleep(500);\

#define END_SEND()       usleep(50000);\
                            if(enable_read)     \
                                enable_read(TRUE); \
                             if(enable_send)     \
                                enable_send(FALSE); \

BOOL
prvbMBPortSerialWrite( UCHAR * pucBuffer, USHORT usNBytes )
{

//    printf("}}}}}}}}}}}}}}}}}}}}}}}---");
    ssize_t         res;
    size_t          left = ( size_t ) usNBytes;
    size_t          done = 0;

    START_SEND();

    while( left > 0 )
    {

        if( ( res = write( iSerialFd, pucBuffer + done, left ) ) == -1 )
        {
            if( errno != EINTR )
            {
                break;
            }
            /* call write again because of interrupted system call. */
            continue;
        }
        done += res;
        left -= res;
    }
    END_SEND();

    return left == 0 ? TRUE : FALSE;
}

BOOL
xMBPortSerialPoll(  )
{
    BOOL            bStatus = TRUE;
    USHORT          usBytesRead;
    int             i;

    while( bRxEnabled )
    {
        if( prvbMBPortSerialRead( &ucBuffer[0], BUF_SIZE, &usBytesRead ) )
        {
            if( usBytesRead == 0 )
            {
                /* timeout with no bytes. */
                break;
            }
            else if( usBytesRead > 0 )
            {
                for( i = 0; i < usBytesRead; i++ )
                {
                    /* Call the modbus stack and let him fill the buffers. */
                    ( void )pxMBFrameCBByteReceived(  ); ///xMBRTUReceiveFSM
                }
                uiRxBufferPos = 0;
            }
        }
        else
        {
            vMBPortLog( MB_LOG_ERROR, "SER-POLL", "read failed on serial device: %s\n",
                        strerror( errno ) );
            bStatus = FALSE;
        }
    }
    if( bTxEnabled )
    {
        while( bTxEnabled )
        {
            ( void )pxMBFrameCBTransmitterEmpty(  );
            /* Call the modbus stack to let him fill the buffer. */
        }
        if( !prvbMBPortSerialWrite( &ucBuffer[0], uiTxBufferPos ) )
        {
            vMBPortLog( MB_LOG_ERROR, "SER-POLL", "write failed on serial device: %s\n",
                        strerror( errno ) );
            bStatus = FALSE;
        }
    }
    return bStatus;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    assert( uiTxBufferPos < BUF_SIZE );
    ucBuffer[uiTxBufferPos] = ucByte;
    uiTxBufferPos++;
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    assert( uiRxBufferPos < BUF_SIZE );
    *pucByte = ucBuffer[uiRxBufferPos];
    uiRxBufferPos++;
    return TRUE;
}
